
package net.badcurrent.ldapodm;

import javax.naming.directory.*;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;

/**
 * Maintains a list of changes for an Object that are to be persisted to the directory.
 * Every object associated with the thread's <code>Session</code> has a corresponding
 * <code>UnitOfWork</code>.
 */
public class UnitOfWork {
    private Object object;
    private Map<String, Attributes> attributesMap;
    private boolean isNew;
    private Attribute objectClasses;

    /**
     * Construct a <code>UnitOfWork</code> for the given Object.
     * @param o
     * @param isNew true if the Object is not persisted to the directory, false if it has
     */
    public UnitOfWork(Object o, boolean isNew) {
        object = o;
        this.isNew = isNew;
    }

    /**
     * Add attributes to be written put for the given property.
     * @param propertyName
     * @param attrs
     */
    public void addAttributes(String propertyName, Attributes attrs) {
        if (attributesMap == null)
            attributesMap = new HashMap<String, Attributes>();

        attributesMap.put(propertyName, attrs);
    }

    /**
     * Add an attribute to be written out for the given property.
     * @param propertyName
     * @param attr
     */
    public void addAttribute(String propertyName, Attribute attr) {
        if (attributesMap == null)
            attributesMap = new HashMap<String, Attributes>();
        Attributes attrs = new BasicAttributes();
        attrs.put(attr);
        attributesMap.put(propertyName, attrs);
    }

    /**
     * Get the <code>Attributes</code> to be persisted to the directory.  This is used
     * if a new object is being persisted to the directory.
     * @return the <code>Attributes</code> to be persisted.
     */
    public Attributes getAttributes() {
        if (attributesMap == null)
            return null;

        Attributes returnAttrs = new BasicAttributes();

        if (objectClasses == null) {
            throw new ConfigurationException("At least one objectClass must be defined");
        }
        returnAttrs.put(objectClasses);

        NamingEnumeration ne;

        for (Attributes attrs : attributesMap.values()) {
            ne = attrs.getAll();
            try {
                while (ne.hasMore()) {
                    returnAttrs.put((Attribute) ne.next());
                }
            } catch (NamingException e) {
                throw new net.badcurrent.ldapodm.NamingException(e);
            }
        }
        return returnAttrs;
    }

    /**
     * Get the <code>Array</code> of <code>ModificationItem</code>s to be persisted
     * to the directory.  This is called if the object already exists in the directory.
     * @return the <code>Array</code> of <code>ModificationItem</code>s
     */
    public ModificationItem[] getModificationItems() {
        if (attributesMap == null)
            return null;

        List<ModificationItem> returnModList = new ArrayList<ModificationItem>();
        for (Attributes attrs : attributesMap.values()) {
            NamingEnumeration ne = attrs.getAll();
            try {
                while (ne.hasMore()) {
                    returnModList.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                            (Attribute) ne.next()));
                }
            } catch (NamingException e) {
                throw new net.badcurrent.ldapodm.NamingException(e);
            }
        }
        ModificationItem[] returnModItems = new ModificationItem[returnModList.size()];
        int i = 0;
        for (ModificationItem mi : returnModList) {
            returnModItems[i] = mi;
            i++;
        }
        return returnModItems;
    }

    /**
     * Get the Object associated with the <code>UnitOfWork</code>
     * @return the associated object.
     */
    public Object getObject() {
        return object;
    }

    /**
     * Associate an Object with the <code>UnitOfWork</code>
     * @param object
     */
    public void setObject(Object object) {
        this.object = object;
    }

    /**
     * Indicate the associated object does not exist in the directory.
     * @return true if the object is not persisted, false if it is
     */
    public boolean isNew() {
        return isNew;
    }

    public void setNew(boolean aNew) {
        isNew = aNew;
    }

    /**
     * Set the LDAP structural object class for the associated Object.
     * @param structuralObjectClass
     */
    public void setStructuralObjectClass(String structuralObjectClass) {
        if (objectClasses == null)
            objectClasses = new BasicAttribute("objectClass");
        objectClasses.add(structuralObjectClass);
    }

    /**
     * Add a supplemental objectClass for the associated Object.
     * @param objectClass
     */
    public void addObjectClass(String objectClass) {
        if (objectClasses == null)
            objectClasses = new BasicAttribute("objectClass");
        objectClasses.add(objectClass);
    }

}
